﻿//#define NETFX2

/*********************************
 $Archive: /Ease/src/Components/Vbyte.SharpOrm/TableEntry.cs $
 $Author: qinjunIt@gmail.com $
 $Modtime: 09-04-15 9:23 $
 $Revision: 43 $
********************************/

using System;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using System.Collections;
using Vbyte.SharpOrm.Config;
using Vbyte.SharpOrm.SqlDb;
using System.Collections.Generic;
using Vbyte.SharpOrm.Adapter;
using System.Web.Script.Serialization;
using System.Text.RegularExpressions;
using Vbyte.DataSource.Cache;

namespace Vbyte.SharpOrm
{
    /// <summary>
    /// SQL数据表实体映射基类
    /// </summary>
    [Serializable]
    public partial class TableEntry : MarshalByRefObject, IXmlSerializable
    {
        /// <summary>
        /// 新建数据表实例对象
        /// </summary>
        public TableEntry()
            : base()
        { }

        /// <summary>
        /// 获取或设置XML序列化临时忽略的属性列表
        /// </summary>
        /// <value>忽略的属性列表</value>
        [CommandIgnore]
        [XmlIgnore]
        [ScriptIgnore]
        public string[] SerializeIgnoreList { get; set; }

        /// <summary>
        /// 获取或设置XML序列化临时的属性列表
        /// </summary>
        /// <value>只序列化集合中的属性</value>
        [CommandIgnore]
        [XmlIgnore]
        [ScriptIgnore]
        public string[] SerializeOnlyList { get; set; }

        /// <summary>
        /// 获取或设置当前实例是否在集合中
        /// </summary>
        /// <value>
        /// 	<c>true</c> 当前实体被放置再集合中; 否则没有在集合中。
        /// </value>
        [CommandIgnore]
        [XmlIgnore]
        [ScriptIgnore]
        public bool IsInCollection { get; set; }

        #region IXmlSerializable 成员

        /// <summary>
        /// 此方法是保留方法，请不要使用。在实现 IXmlSerializable 接口时，应从此方法返回 null（在 Visual Basic 中为 Nothing），如果需要指定自定义架构，应向该类应用 <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/>。
        /// </summary>
        /// <returns>
        /// 	<see cref="T:System.Xml.Schema.XmlSchema"/>，描述由 <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> 方法产生并由 <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> 方法使用的对象的 XML 表示形式。
        /// </returns>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        /*常用序列化配置
         //XmlRootAttribute          根序列化
         //XmlElementAttribute       序列化节点配置
         //XmlIgnoreAttribute        固定忽略
         //XmlIncludeAttribute       固定包含其他未知类型
         //XmlAttributeAttribute     序列化为属性
         */

        /// <summary>
        /// 从对象的 XML 表示形式生成该对象。
        /// </summary>
        /// <param name="reader">对象从中进行反序列化的 <see cref="T:System.Xml.XmlReader"/> 流。</param>
        public virtual void ReadXml(XmlReader reader)
        {
            string beginRootName = reader.Name;
            Type t = this.GetType();

            #region 获取序列化为属性的配置
            if (reader.HasAttributes)
            {
                PropertyInfo[] Properties = GenericCache<PropertyInfo[]>.Get(t.FullName, () => t.GetProperties());
                foreach (PropertyInfo pi in Properties)
                {
                    //序列化为属性
                    if (OrmHelper.HasAttribute(pi, typeof(XmlAttributeAttribute), true))
                    {
                        pi.SetValue(this, Convert.ChangeType(reader.GetAttribute(pi.Name), pi.PropertyType), null);
                    }
                }
            }
            #endregion

            while (reader.Read())
            {
                //XmlNodeType.Element, XmlNodeType.Text, XmlNodeType.EndElement
                if (reader.NodeType == XmlNodeType.Element)
                {
                    //System.Diagnostics.Debug.WriteLine(reader.Name);
                    PropertyInfo cPI = t.GetProperty(reader.Name);

                    #region 属性为数组的数据绑定(暂时忽略)
                    if (cPI.PropertyType.IsArray)
                    {
                        reader.Skip();
                    }
                    #endregion

                    if (cPI != null)
                    {
                        //System.Diagnostics.Debug.WriteLine(cPI.PropertyType);

                        reader.Read();
                        //System.Diagnostics.Debug.WriteLine(reader.Value);
                        cPI.SetValue(this, Convert.ChangeType(reader.Value, cPI.PropertyType), null);

                        reader.Read();
                    }
                }
            }
        }

        /// <summary>
        /// 转义为xml字符
        /// </summary>
        /// <param name="objSource">待转义对象</param>
        /// <returns>如果对象为空则为字符null，否则为相关的字符串表示。</returns>
        public static string Escape2Xml(object objSource)
        {
            if (objSource == null)
            {
                return "null";
            }
            else
            {
                if (objSource.GetType() != typeof(string))
                {
                    return objSource.ToString();
                }
                else
                {
                    return objSource.ToString().Replace("<", "&lt;")
                        .Replace(">", "&gt;")
                        .Replace("&", "&amp;");
                }
            }
        }

        /// <summary>
        /// 将对象转换为其 XML 表示形式。
        /// </summary>
        /// <param name="writer">对象要序列化为的 <see cref="T:System.Xml.XmlWriter"/> 流。</param>
        public virtual void WriteXml(XmlWriter writer)
        {
            //XmlSerializer serializer = new XmlSerializer(this.GetType());
            //serializer.Serialize(writer, this);

            //writer.WriteStartElement("Debug");
            //writer.WriteRaw(this.GetType().FullName);
            //writer.WriteEndElement();

            Type t = this.GetType();
            object propValue = null;

            #region 自身属性序列化
            PropertyInfo[] Properties = GenericCache<PropertyInfo[]>.Get(t.FullName, () => t.GetProperties());
            foreach (PropertyInfo pi in Properties)
            {
                #region 临时忽略属性
                if (SerializeOnlyList != null && SerializeOnlyList.Length > 0)
                {
                    if (!OrmHelper.StringArrayContains(SerializeOnlyList, pi.Name))
                    {
                        continue;
                    }
                }

                if (SerializeIgnoreList != null && SerializeIgnoreList.Length > 0)
                {
                    if (OrmHelper.StringArrayContains(SerializeIgnoreList, pi.Name))
                    {
                        continue;
                    }
                }
                #endregion

                //固定忽略
                if (OrmHelper.HasAttribute(pi, typeof(XmlIgnoreAttribute), true))
                {
                    continue;
                }

                //序列化为属性
                if (OrmHelper.HasAttribute(pi, typeof(XmlAttributeAttribute), true))
                {
                    writer.WriteAttributeString(pi.Name, Escape2Xml(propValue));
                }

                propValue = pi.GetGetMethod().Invoke(this, null);
                if (propValue != null)
                {
                    //System.Diagnostics.Debug.WriteLine(pi.PropertyType);
                    #region 数组对象(暂时忽略序列化)
                    if (pi.PropertyType.IsArray)
                    {
                        continue;
                    }
                    #endregion

                    if (!OrmHelper.IsInstanceBindDefaultValue(pi.PropertyType, propValue))
                    {
                        #region 相关属性序列化
                        //序列化节点配置
                        XmlElementAttribute[] attrs = OrmHelper.GetMemberInfoCustomAttributes<XmlElementAttribute>(pi, true);
                        if (attrs != null && attrs.Length > 0)
                        {
                            XmlElementAttribute attr = attrs[0];
                            writer.WriteStartElement(attr.ElementName);
                            writer.WriteRaw(Escape2Xml(propValue));
                            writer.WriteEndElement();
                        }
                        else
                        {
                            writer.WriteStartElement(pi.Name);
                            writer.WriteRaw(Escape2Xml(propValue));
                            writer.WriteEndElement();
                        }
                        #endregion
                    }
                }
            }
            #endregion

            #region 对不在集合中的扩展泛型实例获取扩展数据
            if (!this.IsInCollection)
            {
                Type CheckType = typeof(IEntryExtension<>);
                //检查是否实现接口IEntryExtension<E>
                if (t.GetInterface(CheckType.Namespace + "." + CheckType.Name, true) != null)
                {
                    try
                    {
                        DictionaryEntry[] DE = t.GetMethod("GetExtenEntryArray").Invoke(this, null) as DictionaryEntry[];
                        foreach (DictionaryEntry e in DE)
                        {
                            writer.WriteStartElement(e.Key.ToString());
                            writer.WriteRaw(Escape2Xml(e.Value));
                            writer.WriteEndElement();
                        }
                    }
                    catch (Exception) { }
                }
            }
            #endregion
        }

        #endregion

        /// <summary>
        /// 获取当前实例的SQL数据表定义
        /// </summary>
        /// <returns>以对象方式表述的SQL表定义</returns>
        public SqlTable GetSqlTableDefine()
        {
            return GetSqlTableDefine(null);
        }

        /// <summary>
        /// 获取当前实例的SQL数据表定义(进一步兼容其他数据库)
        /// </summary>
        /// <param name="sqlBuilder">SQL构建建适配，为null则使用Sql Server语句构建。</param>
        /// <returns>以对象方式表述的SQL表定义</returns>
        public SqlTable GetSqlTableDefine(ISqlBuilderAdapter sqlBuilder)
        {
            SqlTable tab = new SqlTable();
            Type instanceType = this.GetType();
            AdoNetAdapter adp = new AdoNetAdapter(this);
            ISqlDbAdapter dbAdp = adp.GetSqlDbAdapter();

            if (dbAdp == null) throw new System.Configuration.ConfigurationErrorsException("ProviderName:" + adp.ProviderName
                + string.Format("\r\n连接字符串：connectionStrings[\"{0}\"]", adp.ConnectionKey)
                + "\r\n获取该实例类型的ORM数据库适配器失败！");

            object[] typeAttrs = instanceType.GetCustomAttributes(true);
            foreach (object objAttr in typeAttrs)
            {
                if (objAttr is BindTableAttribute)
                {
                    tab.Name = ((BindTableAttribute)objAttr).PrimaryTableName;
                }
                else if (objAttr is CommentAttribute)
                {
                    tab.Description = ((CommentAttribute)objAttr).Description;
                }
            }

            if (string.IsNullOrEmpty(tab.Name)) throw new InvalidOperationException("获取映射表名失败，请检查实例的属性配置！");

            List<SqlTableColumn> columns = new List<SqlTableColumn>();
            List<SqlIndexConfig> indexes = new List<SqlIndexConfig>();
            List<SqlConstraint> constraints = new List<SqlConstraint>();

            List<string> PrimaryKeys = new List<string>();
            List<string> IdentityKyes = new List<string>();
            List<ForeignKeyAttribute> ForeignKeys = new List<ForeignKeyAttribute>();

            PropertyInfo[] Properties = GenericCache<PropertyInfo[]>.Get(instanceType.FullName, () => instanceType.GetProperties());
            foreach (PropertyInfo pi in Properties)
            {
                //对于数组等复合数据直接忽略
                if (pi.PropertyType.IsArray || OrmHelper.HasAttribute(pi, typeof(CommandIgnoreAttribute), true)) continue;

                SqlTableColumn col = new SqlTableColumn();
                bool ignoreColumn = false;
                object[] pAttrs = pi.GetCustomAttributes(true);

                #region 优先设置数据表列名
                object itemMappingObj = Array.Find<object>(pAttrs, new Predicate<object>(delegate(object objAttr)
                    {
                        return objAttr is TableItemAttribute;
                    }));

                if (itemMappingObj != null)
                {
                    TableItemAttribute tAttr = (TableItemAttribute)itemMappingObj;
                    if (string.IsNullOrEmpty(tAttr.BindTable))
                    {
                        col.Name = tAttr.BindField;
                    }
                    else
                    {
                        if (string.Compare(tab.Name, tAttr.BindTable, true) == 0)
                        {
                            col.Name = tAttr.BindField;
                        }
                        else
                        {
                            //不在主数据表中
                            continue;
                        }
                    }
                }
                //属性名即字段名
                if (string.IsNullOrEmpty(col.Name)) col.Name = pi.Name;
                #endregion

                foreach (object attr in pAttrs)
                {
                    #region 自定义属性循环
                    if (attr is XmlIgnoreAttribute)
                    {
                        ignoreColumn = true;
                        break;
                    }
                    else if (attr is PrimaryKeyAttribute)
                    {
                        col.IsPrimary = true;
                        PrimaryKeys.Add(col.Name);
                    }
                    else if (attr is IdentityAttribute)
                    {
                        col.IsIdentity = true;
                        IdentityKyes.Add(col.Name);
                    }
                    else if (attr is NullableAttribute)
                    {
                        col.IsNullable = ((NullableAttribute)attr).DataNullAble;
                    }
                    else if (attr is AutoIncrementAttribute)
                    {
                        col.AutoIncrement = ((AutoIncrementAttribute)attr).GetIncrementConfig();
                    }
                    else if (attr is TableItemAttribute)
                    {
                        continue;
                    }
                    else if (attr is MaxLengthAttribute)
                    {
                        col.MaxLength = ((MaxLengthAttribute)attr).Max;
                    }
                    else if (attr is CommentAttribute)
                    {
                        col.Description = ((CommentAttribute)attr).Description;
                    }
                    else if (attr is DefaultAttribute)
                    {
                        col.Default = ((DefaultAttribute)attr).Value;
                    }
                    else if (attr is SqlDbTypeAttribute)
                    {
                        col.DataType = ((SqlDbTypeAttribute)attr).NativeTypeDefine;
                    }
                    else if (attr is IndexedAttribute)
                    {
                        #region 列索引配置
                        IndexedAttribute idxAttr = (IndexedAttribute)attr;
                        SqlIndexConfig idx = idxAttr.IndexConfig;
                        if (string.IsNullOrEmpty(idx.IndexName))
                        {
                            idx.IndexName = String.Format("IX_{0}_{1}", tab.Name, col.Name);
                        }
                        idx.ColumnIndex = new SqlIndexColumn[] { 
                          new SqlIndexColumn(col.Name, idx.IsDescIndex)
                        };

                        SqlIndexConfig existsConfig = indexes.Find(new Predicate<SqlIndexConfig>(
                                delegate(SqlIndexConfig config)
                                {
                                    return idx.IndexName == config.IndexName;
                                }));


                        if (existsConfig != null)
                        {
                            SqlIndexColumn[] columIdx = new SqlIndexColumn[existsConfig.ColumnIndex.Length + 1];
                            existsConfig.ColumnIndex.CopyTo(columIdx, 0);
                            columIdx[columIdx.Length - 1] = new SqlIndexColumn(col.Name, idx.IsDescIndex);
                            existsConfig.ColumnIndex = columIdx;
                        }
                        else
                        {
                            indexes.Add(idx);
                        }
                        #endregion
                    }
                    else if (attr is ForeignKeyAttribute)
                    {
                        #region 外键约束 待兼容其他数据库
                        ForeignKeyAttribute fKey = (ForeignKeyAttribute)attr;
                        if (string.IsNullOrEmpty(fKey.ForeginKeyColumn)) fKey.ForeginKeyColumn = col.Name;

                        string name = "FK_" + tab.Name + "_" + col.Name;
                        string exp = string.Format("FOREIGN KEY ({0}) REFERENCES {1}{2} ( {3} ) ON UPDATE CASCADE ON DELETE CASCADE",
                            col.Name,
                            "",
                            fKey.ForeignKeyTable,
                            fKey.ForeginKeyColumn);

                        if (sqlBuilder != null && sqlBuilder.HasImplementConstraintExp())
                        {
                            exp = sqlBuilder.BuildConstraintExpression(fKey, "{DBOWNER}", tab.Name, col.Name);
                        }

                        constraints.Add(new SqlConstraint(name, exp));
                        ForeignKeys.Add(fKey);
                        #endregion
                    }
                    else
                    {
                        continue;
                    }
                    #endregion
                }

                if (ignoreColumn == true) continue;

                #region 不支持非Nullable<T>的泛型
                if (pi.PropertyType.IsGenericType)
                {
                    if (pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                    {
                        col.IsNullable = true;
                    }
                    else
                    {
                        continue;
                    }
                }
                #endregion

                //修复标志键、主键为NULL错误
                if ((col.IsIdentity || col.IsPrimary) && col.IsNullable)
                {
                    col.IsNullable = false;
                }

                //设置数据类型
                if (string.IsNullOrEmpty(col.DataType))
                {
                    col.DataType = dbAdp.GetNativeType(pi.PropertyType, col.MaxLength);
                }
                else
                {
                    /* \w+\((?<max>\d+)\)
                     char(20)
                     varchar(50)
                     nvarchar(20)
                     nchar(20)
                     */
                    Match m = Regex.Match(col.DataType, "\\w+\\((?<max>\\d+)\\)", RegexOptions.IgnoreCase);
                    if (m.Success)
                    {
                        col.MaxLength = Convert.ToInt32(m.Groups["max"].Value);
                    }
                }

                columns.Add(col);
            }

            #region 设置表中数据定义
            tab.Columns = columns.ToArray();
            tab.Indexes = indexes.ToArray();
            tab.Constains = constraints.ToArray();
            tab.PrimaryKeys = PrimaryKeys.ToArray();
            tab.IdentityKeys = IdentityKyes.ToArray();
            tab.ForeignKeys = ForeignKeys.ToArray();
            tab.MappingEntryType = instanceType;
            #endregion

            adp.Dispose();
            return tab;
        }


        private ISqlConstrains _entryConstrains = null;

        /// <summary>
        /// 设置该实例上的约束和筛选设置
        /// </summary>
        /// <param name="constrains">约束和筛选设置配置</param>
        /// <returns>应用约束和筛选设置后的实例</returns>
        public TableEntry WithConstrains(ISqlConstrains constrains)
        {
            _entryConstrains = constrains;
            return this;
        }

        /// <summary>
        /// 获取当前实例的约束和筛选设置
        /// </summary>
        /// <returns>如果存在则返回，否则为Null。</returns>
        public ISqlConstrains GetConstrains()
        {
            return _entryConstrains;
        }

    }

    /// <summary>
    /// 实体不存在的异常
    /// </summary>
    public class NotExistException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NotExistException"/> class.
        /// </summary>
        public NotExistException()
            : base()
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NotExistException"/> class.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        public NotExistException(string msg)
            : base(msg)
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="NotExistException"/> class.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        /// <param name="innerExp">The inner exp.</param>
        public NotExistException(string msg, Exception innerExp)
            : base(msg, innerExp)
        { }
    }
}